{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# [前端面试题：JS中的let和var的区别](https://www.cnblogs.com/fly_dragon/p/8669057.html)\n",
    "\n",
    "ES6 新增了`let`命令，用来声明局部变量。它的用法类似于`var`，但是所声明的变量，只在`let`命令所在的代码块内有效，而且有暂时性死区的约束。\n",
    "\n",
    "先看个`var`的常见变量提升的面试题目："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "undefined\n",
      "10\n",
      "99\n"
     ]
    }
   ],
   "source": [
    "var a = 99;             // 全局变量a\n",
    "f();                    // f是函数，虽然定义在调用的后面，但是函数声明会提升到作用域的顶部。 \n",
    "console.log(a);         // a=>99,  此时是全局变量的a\n",
    "function f() {\n",
    "    console.log(a);     // 当前的a变量是下面变量a声明提升后，默认值undefined\n",
    "    var a = 10;\n",
    "    console.log(a);     // a => 10\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ES6可以用let定义块级作用域变量\n",
    "在ES6之前，我们都是用var来声明变量，而且JS只有函数作用域和全局作用域，没有块级作用域，所以`{}`限定不了var声明变量的访问范围。\n",
    "\n",
    "例如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "9\n"
     ]
    }
   ],
   "source": [
    "{ \n",
    "  var i = 9;\n",
    "} \n",
    "console.log(i);  // 9"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "ES6新增的let，可以声明块级作用域的变量。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "9\n"
     ]
    }
   ],
   "source": [
    "{ \n",
    "  let i = 9;     // i变量只在 花括号内有效！！！\n",
    "} \n",
    "console.log(i);  // Uncaught ReferenceError: i is not defined"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## let 配合for循环的独特应用\n",
    "`let`非常适合用于 `for` 循环内部的块级作用域。JS中的for循环体比较特殊，每次执行都是一个全新的独立的块作用域，用let声明的变量传入到 for循环体的作用域后，不会发生改变，不受外界的影响。\n",
    "\n",
    "看一个常见的面试题目："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10\n",
      "10\n",
      "10\n",
      "10\n",
      "10\n",
      "10\n",
      "10\n",
      "10\n",
      "10\n",
      "10\n"
     ]
    }
   ],
   "source": [
    "(()=>{\n",
    "    for (var i = 0; i <10; i++) {  \n",
    "      setTimeout(function() {  // 同步注册回调函数到 异步的 宏任务队列。\n",
    "        console.log(i);        // 执行此代码时，同步代码for循环已经执行完成\n",
    "      }, 0);\n",
    "    }\n",
    "})();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果把 var改成 let声明："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n",
      "1\n",
      "2\n",
      "3\n",
      "4\n",
      "5\n",
      "6\n",
      "7\n",
      "8\n",
      "9\n"
     ]
    }
   ],
   "source": [
    "(()=>{\n",
    "    // i虽然在全局作用域声明，但是在for循环体局部作用域中使用的时候，变量会被固定，不受外界干扰。\n",
    "    for (let i = 0; i < 10; i++) { \n",
    "      setTimeout(function() {\n",
    "        console.log(i);    //  i 是循环体内局部作用域，不受外界影响。\n",
    "      }, 0);\n",
    "    }\n",
    "})();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## let没有变量提升与暂时性死区\n",
    "用`let`声明的变量，不存在变量提升。而且要求必须 等`let`声明语句执行完之后，变量才能使用，不然会报`Uncaught ReferenceError`错误。\n",
    "\n",
    "例如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "Identifier 'aicoder' has already been declared",
     "output_type": "error",
     "traceback": [
      "evalmachine.<anonymous>:1",
      "//console.log(aicoder);    // 错误：Uncaught ReferenceError ...",
      "^",
      "",
      "SyntaxError: Identifier 'aicoder' has already been declared",
      "    at evalmachine.<anonymous>:1:1",
      "    at Script.runInThisContext (vm.js:131:20)",
      "    at Object.runInThisContext (vm.js:297:38)",
      "    at run ([eval]:1054:15)",
      "    at onRunRequest ([eval]:888:18)",
      "    at onMessage ([eval]:848:13)",
      "    at process.emit (events.js:315:20)",
      "    at emit (internal/child_process.js:881:12)",
      "    at processTicksAndRejections (internal/process/task_queues.js:85:21)"
     ]
    }
   ],
   "source": [
    "//console.log(aicoder);    // 错误：Uncaught ReferenceError ...\n",
    "let aicoder = 'aicoder.com';\n",
    "// 这里就可以安全使用aicoder\n",
    "//console.log(aicoder);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "ename": "ReferenceError",
     "evalue": "aicoder is not defined",
     "output_type": "error",
     "traceback": [
      "evalmachine.<anonymous>:1",
      "console.log(aicoder);",
      "            ^",
      "",
      "ReferenceError: aicoder is not defined",
      "    at evalmachine.<anonymous>:1:13",
      "    at Script.runInThisContext (vm.js:131:20)",
      "    at Object.runInThisContext (vm.js:297:38)",
      "    at run ([eval]:1054:15)",
      "    at onRunRequest ([eval]:888:18)",
      "    at onMessage ([eval]:848:13)",
      "    at process.emit (events.js:315:20)",
      "    at emit (internal/child_process.js:881:12)",
      "    at processTicksAndRejections (internal/process/task_queues.js:85:21)"
     ]
    }
   ],
   "source": [
    "console.log(aicoder);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "ES6 明确规定，如果区块中存在let和const命令，这个区块对这些命令声明的变量，从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量，就会报错。\n",
    "总之，在代码块内，使用let命令声明变量之前，该变量都是不可用的。这在语法上，称为“暂时性死区”（temporal dead zone，简称 TDZ）。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## let变量不能重复声明\n",
    "let不允许在相同作用域内，重复声明同一个变量。否则报错：`Uncaught SyntaxError: Identifier 'XXX' has already been declared`\n",
    "\n",
    "例如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "Identifier 'a' has already been declared",
     "output_type": "error",
     "traceback": [
      "evalmachine.<anonymous>:2",
      "let a = 'sss';",
      "    ^",
      "",
      "SyntaxError: Identifier 'a' has already been declared",
      "    at new Script (vm.js:99:7)",
      "    at createScript (vm.js:249:10)",
      "    at Object.runInThisContext (vm.js:297:10)",
      "    at run ([eval]:1054:15)",
      "    at onRunRequest ([eval]:888:18)",
      "    at onMessage ([eval]:848:13)",
      "    at process.emit (events.js:315:20)",
      "    at emit (internal/child_process.js:881:12)",
      "    at processTicksAndRejections (internal/process/task_queues.js:85:21)"
     ]
    }
   ],
   "source": [
    "let a = 0;\n",
    "let a = 'sss';\n",
    "// Uncaught SyntaxError: Identifier 'a' has already been declared"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 总结\n",
    "ES6的let让js真正拥有了块级作用域，也是向这更安全更规范的路走，虽然加了很多约束，但是都是为了让我们更安全的使用和写代码。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# [补充：加“var”和不加“var”的区别](https://www.cnblogs.com/liuna/p/6140901.html)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Javascript声明变量的时候，虽然用var关键字声明和不用关键字声明，很多时候运行并没有问题，但是这两种方式还是有区别的。可以正常运行的代码并不代表是合适的代码。\n",
    "\n",
    "var num = 1；\n",
    "\n",
    "是在当前域中声明变量. 如果在方法中声明，则为局部变量（local variable）；如果是在全局域中声明，则为全局变量。\n",
    "\n",
    "而 num = 1；\n",
    "\n",
    "事实上是对属性赋值操作。首先，它会尝试在当前作用域链（如在方法中声明，则当前作用域链代表全局作用域和方法局部作用域etc。。。）中解析 num； 如果在任何当前作用域链中找到num，则会执行对num属性赋值； 如果没有找到num，它才会在全局对象（即当前作用域链的最顶层对象，如window对象）中创造num属性并赋值。\n",
    "\n",
    "注意！它并不是声明了一个全局变量，而是创建了一个全局对象的属性。\n",
    "\n",
    "即便如此，可能你还是很难明白“变量声明”跟“创建对象属性”在这里的区别。事实上，Javascript的变量声明、创建属性以及每个Javascript中的每个属性都有一定的标志说明它们的属性----如只读（ReadOnly）不可枚举（DontEnum）不可删除（DontDelete）等等。\n",
    "\n",
    "由于变量声明自带不可删除属性，比较var num = 1 跟 num = 1，前者是变量声明，带不可删除属性，因此无法被删除；后者为全局变量的一个属性，因此可以从全局变量中删除。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "// num1为全局变量，num2为window的一个属性\n",
    "var num1 = 1;\n",
    "num2 = 2;\n",
    "// delete num1;  无法删除\n",
    "// delete num2;  删除\n",
    "\n",
    "function model(){\n",
    "    var num1 = 1; // 本地变量\n",
    "    num2 = 2;     // window的属性\n",
    "\n",
    "    // 匿名函数\n",
    "    (function(){\n",
    "           var num = 1; // 本地变量\n",
    "           num1 = 2; // 继承作用域（闭包）\n",
    "           num3 = 3; // window的属性\n",
    "    }())\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "model();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "PS. 在ECMAScript5标准中，有一种“严格模式”（Strict Mode）。在严格模式中，为未声明的标识符赋值将会抛引用错误，因此可以防止意外的全局变量属性的创造。目前一些浏览器的新版本已经支持。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Javascript (Node.js)",
   "language": "javascript",
   "name": "javascript"
  },
  "language_info": {
   "file_extension": ".js",
   "mimetype": "application/javascript",
   "name": "javascript",
   "version": "13.13.0"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "165px"
   },
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
